/*
 * MusicLabs, Copyright (C) 2008-2009 Felippe Ragghianti
 * http://code.google.com/p/musiclabs
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 */


package br.com.music.player;

import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import java.util.Random;

import br.com.music.Pitch;
import br.com.music.scale.Scale;

/**
 * The player is meant to translate from br.com.music entitites
 * to playable text; then Player bounces the music to the MIDI
 * sequencer.
 *
 * @version $LastChangedRevision$
 */
public class Player {

    // XXX Player could possibly be a Singleton since the sequencer is scarce.
    // XXX Also, since the sequencer is scarce, Player will need to stack
    // requests asynchronously.
    // XXX Should not receive a Scale. Should receive Music instead.

    /**
     * The playable text (MIDI) player.
     */
    private org.jfugue.Player p;

    /**
     * The music itself.
     */
    private String playableText;

    /**
     * Class constructor.
     */
    public Player()
    {
        p = new org.jfugue.Player();
    }

    // FIXME plz fix javadoc bellow: in which octave is it going to be played?

    /**
     * Play a finite sequence of notes.
     *
     * @param scale A sequence of notes.
     */
    public void play(Scale scale)
    {
        playableText = getPlayableText(scale, 4, Instrument.PIANO);
        p.play(playableText);
    }

    /**
     * Play a finite sequence of noves in the given octave.
     *
     * @param scale A sequence of notes.
     * @param octave Octave index.
     */
    public void play(Scale scale, Integer octave)
    {
        playableText = getPlayableText(scale, octave, Instrument.PIANO);
        p.play(playableText);
    }

    /**
     * Play scale in given octave with given instrument.
     *
     * @param scale A generic scale.
     * @param octave Octave index.
     * @param instrument Instrument object.
     */
    public void play(Scale scale, Integer octave, Instrument instrument)
    {
        playableText = getPlayableText(scale, octave, instrument);
        p.play(playableText);
    }

    /**
     * TODO javadoc
     * 
     * @param element
     */
    public void play(Playable entity)
    {
        p.play(entity.toString());
    }

    /**
     * TODO javadoc
     *
     * @param element
     */
    public void play(Playable... entities)
    {
        playableText = "";
        for(Playable entity : entities) {
            playableText += entity;
        }
        p.play(playableText);
    }


    /**
     * Save playable text to file on disk.
     *
     * @param filePath Path to file.
     * @throws IOException
     */
    public void save(String filePath) throws IOException
    {
        p.saveMidi(playableText, new File(filePath));
    }

    /**
     * Creates playable text from the combination of several
     * elements of the music.
     *
     * @param scale A generic scale.
     * @param octave Octave index.
     * @param instrument Instrument object.
     * @return Returns the playable text.
     */
    private String getPlayableText(Scale scale, Integer octave, Instrument instrument)
    {
        // getting scale notes
        List<Pitch> notes = scale.getNotes();
        Pitch[] notesArray = notes.toArray(new Pitch[]{});

        // setting instrument
        StringBuffer sb = new StringBuffer();
        sb.append("I[");
        sb.append(instrument);
        sb.append("] ");

        // adjusting octaves
        int index = notes.lastIndexOf(Pitch.DO);
        if(index == -1) {
            index = notes.lastIndexOf(Pitch.DOS);
        }
        for (int i = 0; i < index; i++) {
            sb.append(notesArray[i].toString());
            sb.append(octave);
            sb.append(" ");
        }
        ++octave;
        for (int i = index; i < notesArray.length; i++) {
            sb.append(notesArray[i].toString());
            sb.append(octave);
            if(i == notesArray.length - 1) {
                // setting greater duration for last note
                sb.append("w");
            }
            sb.append(" ");
        }
        return sb.toString();
    }

    /* FIXME: there is no such element "music" yet. */
    /**
     * Create a random Music.
     *
     * @note XXX randMusic could return an object Music.
     *       XXX randMusic(Integer qt, TimeSignature sig)
     *       XXX should be put on Music instead.
     *
     * @param scale A generic scale.
     * @param qt Number of notes in the scale.
     * @param octave Octave index.
     * @throws IOException
     */
    public void randMusic(Scale scale, Integer qt, Integer octave)
            throws IOException
    {
        List<Pitch> notes = scale.getNotes();
        int size = notes.size();
        Random r = new Random(Calendar.getInstance().getTimeInMillis());
        //String[] durations = new String[]{"W","H","Q","I", "S", "T", "X", "O"};
        // TODO: TimeSignature should be used here instead.
        String[] durations = new String[]{"W","H","Q","I"};
        Integer[] octaves = new Integer[]{4,5};
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < qt; i++) {
            int randomIndex = r.nextInt(size);
            sb.append(notes.get(randomIndex).toString());
            sb.append(octaves[r.nextInt(octaves.length)]);
            sb.append(durations[r.nextInt(durations.length)]);
            sb.append(" ");
        }
        playableText = sb.toString();
        p.play(playableText);
    }

}